package com.dingwen.treasure.web.excption;

import cn.hutool.core.util.ObjectUtil;
import com.dingwen.treasure.base.constant.GlobalConstant;
import com.dingwen.treasure.base.exception.AbstractBaseRuntimeException;
import com.dingwen.treasure.base.exception.BaseExceptionHandler;
import com.dingwen.treasure.base.pojo.vo.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 全局异常处理 </br>
 * <p>
 * 当 @RestControllerAdvice 出现在三方  jar 包中时因为没有 import，没有配置三方 jar 的包扫描的话会导致全局异常失效
 * 通过启动类指定 scanBasePackages 即可，若不方便指定包扫描则可以同时指定类以及扫描的路径
 * 如:@SpringBootApplication(scanBasePackageClasses = {GlobalExceptionHandler.class},scanBasePackages = "com.dingwen.*")
 * 注意 springboot 默认扫描 启动类所在包以及子包，若单独配置类一个，会导致默认配置失效，可以结合多个配置，灵活解决问题
 * </p>
 *
 *
 * <p>
 *
 * @author dingwen
 * "@RestControllerAdvice" 相当于 @ControllerAdvice 和 @ResponseBody
 * </p>
 * @date 2021.06.29 10:18
 */
@RestControllerAdvice
@Slf4j
@RequiredArgsConstructor
public class GlobalExceptionHandler {


    /**
     * 异常处理 map
     */
    private final Map<String, BaseExceptionHandler> EXCEPTION_HANDLER_MAP;

    /**
     * 默认异常处理
     */
    private static final String DEFAULT_EXCEPTION_HANDLER_BEAN_NAME = "java.lang.Exception";

    /**
     * 自定义异常处理
     */
    private static final String CUSTOM_EXCEPTION_HANDLER_BEAN_NAME = "com.dingwen.treasure.base.exception.AbstractBaseRuntimeException";

    @ExceptionHandler(value = Exception.class)
    public Result<String> exceptionHandler(HttpServletRequest request, Exception exception) {
        StringBuilder builder = new StringBuilder();
        //接口地址
        String url = request.getRequestURI().substring(request.getContextPath().length());
        //堆栈信息
        String stackTracesMsg = printStackTraces(exception);
        //userId
        String userId = request.getHeader(GlobalConstant.TREASURE_HEAD_USER_ID);
        builder.append("userId: ").append(userId).append("\n")
                .append("requestUri: ")
                .append(url)
                .append("\n")
                .append(stackTracesMsg);

        // 堆栈日志打印
        log.error(builder.toString());

        // 异常处理： 统一结果返回
        String fullClassName = exception instanceof AbstractBaseRuntimeException ?
                CUSTOM_EXCEPTION_HANDLER_BEAN_NAME
                : exception.getClass().getName();

        BaseExceptionHandler exceptionHandler = EXCEPTION_HANDLER_MAP.get(fullClassName);
        if (ObjectUtil.isEmpty(exceptionHandler)) {
            exceptionHandler = EXCEPTION_HANDLER_MAP.get(DEFAULT_EXCEPTION_HANDLER_BEAN_NAME);
        }

        return exceptionHandler.buildResultByException(exception);
    }

    /**
     * 最多打印7层堆栈信息
     *
     * @param e 异常
     * @return 异常信息
     */
    private String printStackTraces(Exception e) {
        StackTraceElement[] stackTraces = e.getStackTrace();
        StringBuilder builder = new StringBuilder();
        builder.append(e.getClass().getName())
                .append(": ")
                .append(e.getLocalizedMessage())
                .append("\n");
        for (StackTraceElement stackTrace : stackTraces) {
            String lineMsg = "         at ";
            lineMsg = lineMsg + stackTrace.getClassName()
                    + "(" + stackTrace.getFileName() + ":"
                    + stackTrace.getLineNumber() + ")\n";
            builder.append(lineMsg);
        }
        return builder.toString();
    }

}
